Skip to content

Optimizer: don't inline named functions in debug builds#19548

Open
auduchinok wants to merge 11 commits intodotnet:mainfrom
auduchinok:inlineNamedFunctions
Open

Optimizer: don't inline named functions in debug builds#19548
auduchinok wants to merge 11 commits intodotnet:mainfrom
auduchinok:inlineNamedFunctions

Conversation

@auduchinok
Copy link
Copy Markdown
Member

@auduchinok auduchinok commented Apr 3, 2026

inline functions are fully inlined on the call sites by F# compiler. This means the source information and actual calls are lost, so it's very hard to reliably debug code using such functions.

This PR prevents inlining inline functions in debug builds. This improves debugging experience by allowing setting breakpoints inside such functions and allowing stepping into them and checking the function arguments. It also allows IDEs to analyze the produced IL more reliably during debug.

The implementation handles two distinct cases. When possible, a normal call to the existing method is emitted. When a function contains SRTPs and no callable method is produced, a specialized method is created for each type instantiation.

An example inline function that can be called directly:

let inline add a =
    a + 1

[<EntryPoint>]
let main _ =
    let i = add 1
    0
Screenshot 2026-04-03 at 21 18 51

The existing method is called:
Screenshot 2026-04-03 at 21 18 43

An inline function with SRTPs:

let inline add a b =
    a + b

[<EntryPoint>]
let main _ =
    let i = add 1 2
    0
Screenshot 2026-04-03 at 20 57 08

A specialized method is produced and called:
Screenshot 2026-04-03 at 20 57 22

Open questions:

  • Should we annotate specialized functions from other assemblies somehow? DebuggerNonUserCode?
  • Should we whitelist something from FSharp.Core, like built-in operators?

Implements fsharp/fslang-suggestions#824 for debug builds.
Fixes #9555.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 3, 2026

❗ Release notes required


✅ Found changes and release notes in following paths:

Change path Release notes path Description
src/Compiler docs/release-notes/.FSharp.Compiler.Service/11.0.100.md

@auduchinok auduchinok force-pushed the inlineNamedFunctions branch from ed866e5 to d607132 Compare April 7, 2026 13:09
@auduchinok auduchinok force-pushed the inlineNamedFunctions branch from bb4fe70 to 9377496 Compare April 7, 2026 13:25
@auduchinok auduchinok force-pushed the inlineNamedFunctions branch from 9377496 to 257c702 Compare April 7, 2026 13:27
@T-Gro
Copy link
Copy Markdown
Member

T-Gro commented Apr 9, 2026

re: Debugging

I would rely on people choosing to do "Just My Code" or not, in general.
For existing FSharp.Core functions, this might be indeed way too intrusive - are you thinking about handrolling the attributes, or a more general treatment when compiling fslib?

I would assume most of the "ugly to debug" fslib code uses statically optimized switches and is forced to be inlined even after this feature?

@auduchinok
Copy link
Copy Markdown
Member Author

auduchinok commented Apr 9, 2026

I would rely on people choosing to do "Just My Code" or not, in general.

Yes, IDE settings is going to be the primary way to control it. The problem is when an SRTP function from another assembly gets a specialized definition, the resulting method becomes a part of the user assembly. That's the reason I'm considering adding an attribute like DebuggerNonUserCode or another way to differentiate.

For existing FSharp.Core functions, this might be indeed way too intrusive - are you thinking about handrolling the attributes, or a more general treatment when compiling fslib?

I'm considering keeping inlining for functions in these modules:

  • Microsoft.FSharp.Core.LanguagePrimitives.IntrinsicOperators
  • Microsoft.FSharp.Core.Operators
  • Microsoft.FSharp.Core.Operators.Checked
  • Microsoft.FSharp.Core.Operators.OperatorIntrinsics
  • Microsoft.FSharp.NativeInterop.NativePtr

That would allow to workaround the issue with NoDynamicInvocation and would also hide the most of the basic functions, like not or +, that the user is likely not going to step into anyway.

@auduchinok
Copy link
Copy Markdown
Member Author

auduchinok commented Apr 9, 2026

I've checked all NoDynamicInvocation usages on GitHub and the only ones that are problematic (i.e. hidden by the signature) are the ones in the FSharp.Core modules above, so I think it's safe enough to to whitelist these modules + check the attribute.

@T-Gro
Copy link
Copy Markdown
Member

T-Gro commented Apr 9, 2026

Ok, how do you want to whitelist those?
A module-level attribute ([<InlineEvenDebug>] like), or listing them in the compiler by name?

I agree with the selection, there is nothing surprising inside of those module's functions.

For the specialized SRTP definition, wouldn't [<CompilerGenerated>] work here?

@auduchinok
Copy link
Copy Markdown
Member Author

Ok, how do you want to whitelist those?
A module-level attribute ([] like), or listing them in the compiler by name?

I'm going to try the second approach first, so it could work with older FSharp.Core too.

@auduchinok auduchinok force-pushed the inlineNamedFunctions branch from 8049b31 to a48fa0e Compare April 9, 2026 14:55
@auduchinok auduchinok force-pushed the inlineNamedFunctions branch from a48fa0e to 0c16c35 Compare April 9, 2026 15:07
@auduchinok auduchinok force-pushed the inlineNamedFunctions branch from 5cc1c28 to 1a0b4d6 Compare April 10, 2026 15:29
@auduchinok auduchinok force-pushed the inlineNamedFunctions branch from 1a0b4d6 to e5e7134 Compare April 10, 2026 15:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: New

Development

Successfully merging this pull request may close these issues.

F# .net core debugger: Inline functions don't work as expected in debugger even in Debug builds

2 participants